#include "GUIGantry.h"

GUIGantry::GUIGantry(int startX, int startY, QGraphicsScene *scene)
: GUIBase()
{ //constructor sets default values 
	loadImage("Resources/Sprites/Gantry/Gantry.png");
	this->loadMultImages("Resources/Sprites/Gantry/Exploding/");

	state = GantryIdle;
	vertState = GantryStable;
	dropAndPickUpBin = false;
	dropAndLetGoOfBin = false;

	exploding = false;

	this->setOriginCenter(); // Change origin to center of image.

	this->setX(startX);
	this->setY(startY);
	this->setZValue(ZVALUE_GANTRY);

	this->setMovementSpeed(GANTRY_MOVEMENT_SPEED);
	this->setRotationSpeed(GANTRY_ROTATION_SPEED);
	this->setScaleSpeed(GANTRY_VERT_SPEED);
}

QPointF GUIGantry::GetBinLocation(int binNumber)
{ //will return coordinates matching correct bin number so gantry knows where to move
	switch(binNumber)
	{
	case 0:
		return QPoint(460, 476); //bins have set location 
		break;
	case 1:
		return QPoint(500, 476);
		break;
	case 2:
		return QPoint(540, 476);
		break;
	case 3:
		return QPoint(580, 476);
		break;
	case 4:
		return QPoint(620, 476);
		break;
	case 5:
		return QPoint(660, 476);
		break;
	case 6:
		return QPoint(700, 476);
		break;
	case 7:
		return QPoint(740, 476);
		break;
	}
	return QPoint(0,0);
}

void GUIGantry::GiveBinList(vector<Bin*> binList)
{ //recieves list of bins for factory  
	bins = binList;
}

Bin* GUIGantry::GetBinRef(int binNumber)
{
	if (bins.size() <= binNumber)
	{
		cerr << "GUIGantry: Trying to get a reference to a bin that hasn't been created yet." << endl;
		return NULL;
	}
	return bins[binNumber];
	//returns reference number for each bin in list
}

void GUIGantry::GiveFeederList(vector<GUIFeeder*> feederList)
{
	feeders = feederList;
	//recieves list of feeder references for factory 
}

GUIFeeder* GUIGantry::GetFeederRef(int feederNumber)
{
	if (feeders.size() <= feederNumber)
	{
		debugPrint("GUIGantry: Trying to get a reference to a feeder that hasn't been created yet.");
		return NULL;
	}
	return feeders[feederNumber];
	//returns feeder reference for specific index 
}

void GUIGantry::MoveToBinLocation(int binNumber)
{
	//moves gantry to specific bin location 
	QPointF point = GetBinLocation(binNumber);
	moveTo(point.x(), point.y());
}

void GUIGantry::DropDown(bool pickUpObj, bool letGoOfObj)
{
	//Watches out for the invalid case.
	if (pickUpObj && letGoOfObj)
	{
		qDebug("Invalid parameters given to GUIGantry::DropDown (true/true)");
		return;
	}
		
	vertState = GantryDropping;
	dropAndPickUpBin = pickUpObj;
	dropAndLetGoOfBin = letGoOfObj;
	this->scaleOverTime(0.7);
	emit signalBeginDescending(1.0, this->scaleSpeed);
	//animation when gantry is picking up or letting go
}

void GUIGantry::PullUp()
{ //animation when gantry is rising 
	vertState = GantryRising;
	this->scaleOverTime(1.0);
	emit signalBeginAscending(1.3, this->scaleSpeed);
}

void GUIGantry::LatchOntoBin()
{
	//called whenever gantry needs to connect to bin and parent it 
	currentBinRef->setFlag(QGraphicsItem::ItemStacksBehindParent);
	currentBinRef->setParentItem(this);
	currentBinRef->setPos(0, 0);
	hasBin = true;
	QObject::connect(this, SIGNAL(signalBeginAscending(qreal, qreal)), currentBinRef, SLOT(beganAscending(qreal, qreal)));
	QObject::connect(this, SIGNAL(signalBeginDescending(qreal, qreal)), currentBinRef, SLOT(beganDescending(qreal, qreal)));
}


void GUIGantry::UnlatchFromBin()
{
	//resets bin parent to null as gantry lets go of bin 
	currentBinRef->setParentItem(NULL);
	currentBinRef->moving = false;
	currentBinRef->setPos(this->x(), this->y());
	hasBin = false;
	QObject::disconnect(SIGNAL(signalBeginAscending(qreal, qreal)), currentBinRef, SLOT(beganAscending(qreal, qreal)));
	QObject::disconnect(SIGNAL(signalBeginDescending(qreal, qreal)), currentBinRef, SLOT(beganDescending(qreal, qreal)));
}

void GUIGantry::ResumeNormative()
{ //restarts/fixes gantry after it has been broken 
	switch(state)
	{
		case GantryGoingIdle:
		case GantryIdle:
			DoGoIdle();
			break;
		case GantryPickingUpFullBin:
			PickUpFullBin();
			break;
		case GantryMovingToFeeder:
			GoToFeeder();
			break;
		case GantryMovingBinToPurgeArea:
			PutBinInPurgeArea();
			break;
		case GantryDroppingOffEmptyBin:
			DropOffEmptyBin();
			break;
		case GantryGettingEmptyBin:
			GetEmptyBin();
			break;
		case GantryFillingEmptyBin:
			FillEmptyBin();
			break;
		case GantryDroppingOffNewBin:
			DropOffNewBin();
			break;
	}
}

void GUIGantry::DoBreak()
{ //called by button in non norm panel 
	GUIBase::DoBreak();

	moving = false; // Stop what I'm doing.
	rotating = false;
	scaling = false;
	
	//this->DoExplode();
}

void GUIGantry::DoFix()
{//called after gantry has broken and is now being fixed
	GUIBase::DoFix();

	this->StopExploding();
	this->ResumeNormative();
}

void GUIGantry::DoExplode()
{ //bounces gantry around screeen 
	this->exploding = true;
	currentAnim = 1;
	blackAndWhite = false;
	
	this->doneMoving();
	this->doneRotating();
	this->doneScaling();
}

void GUIGantry::StopExploding()
{ //stops the animation of gantry exploding/bouncing around screen 
	exploding = false;

	currentAnim = 0;
	currentFrame = 0;
	
	moving = false;
	scaling = false;

	this->rotateOverTime(0, (rotation() >= 180));
	this->imgScale = 1;

	this->setMovementSpeed(GANTRY_MOVEMENT_SPEED);
	this->setRotationSpeed(GANTRY_ROTATION_SPEED);
	this->setScaleSpeed(GANTRY_VERT_SPEED);
}

void GUIGantry::DoGoIdle()
{ //sets state and moves gantry to base location 
	state = GantryGoingIdle;

	if (!exploding)
		moveTo(GANTRY_IDLE_X, GANTRY_IDLE_Y);
}

void GUIGantry::DoGUIMoveToFeeder(int binNumber, int feederNumber,bool topLane)
{
	// Set what the gantry is currently dealing with.
	currentBinNum = binNumber;
	currentFeederNum = feederNumber;
	currentLane = topLane;

	// Get bin and feeder references from list and hold on to them.
	currentBinRef = this->GetBinRef(binNumber);
	currentFeederRef = this->GetFeederRef(feederNumber);

	// Begin process.
	PickUpFullBin();
}

void GUIGantry::PickUpFullBin()
{ //sets state and moves to full bin 
	state = GantryPickingUpFullBin;

	if (!exploding)
		moveTo(currentBinRef->scenePos().x(), currentBinRef->scenePos().y());
}


void GUIGantry::GoToFeeder()
{
	//sets state and moves to correct feeder location 
	state = GantryMovingToFeeder;
	moveTo(currentFeederRef->x(), currentFeederRef->y());
}

void GUIGantry::DumpBinIntoFeeder()
{
	//drops bin parts into feeder
	currentFeederRef->HereAreBinParts(currentBinRef->GetParts());
	currentBinRef->EmptyBin();
}

void GUIGantry::PutBinInPurgeArea()
{
	//puts bin down next to feeder
	state = GantryMovingBinToPurgeArea;
	moveTo(currentFeederRef->x() + 40, currentFeederRef->y());
}

void GUIGantry::DropOffEmptyBin()
{
	//puts bin down after it is empty in original spot 
	state = GantryDroppingOffEmptyBin;

	// Need coordinates of where to drop off the bin based off of its number.
	QPointF point = GetBinLocation(currentBinNum);
	moveTo(point.x(), point.y());
}

void GUIGantry::DoGUIReplaceBin(PartType partType, int binNumber)
{ //called to bring replacement bins in original locations 
	debugPrint("Gantry: Replacing bin");

	currentBinNum = binNumber;
	currentBinRef = GetBinRef(binNumber);
	newBinType = partType;

	GetEmptyBin();
}


void GUIGantry::GetEmptyBin()
{
	//sets state and moves to current bin loc 
	state = GantryGettingEmptyBin;

	if (!exploding)
		moveTo(currentBinRef->scenePos().x(), currentBinRef->scenePos().y());
}

void GUIGantry::FillEmptyBin()
{
	//sets state and moves back to idle pos 
	state = GantryFillingEmptyBin;
	moveTo(GANTRY_IDLE_X, GANTRY_IDLE_Y);
}

void GUIGantry::FillBinWithParts()
{
	currentBinRef->FillBinWithNewParts(newBinType);

	// Gantry is going to carry these back onto the screen while in the air so they should be bigger (they shrink when put back on the ground)
	for (int i = 0; i < currentBinRef->GetPartCount(); i++)
	{
		currentBinRef->GetParts()[i]->imgScale = 1.3;
	}
}

void GUIGantry::DropOffNewBin()
{
	//sets new state and moves to bin location 
	state = GantryDroppingOffNewBin;

	// Need coordinates of where to drop off the bin based off of its number.
	QPointF point = GetBinLocation(currentBinNum);
	moveTo(point.x(), point.y());
}

void GUIGantry::setAgent(GantryAgent* agent)
{
	gantryAgent = agent;
	this->setGUIBaseAgent(agent);
}


void GUIGantry::doneMoving()
{
	if (!exploding)
	{
		switch(state)
		{
			case GantryIdle:
				break;
			case GantryGoingIdle:
				state = GantryIdle;
				break;
			case GantryPickingUpFullBin:
				DropDown(true, false);
				break;
			case GantryMovingToFeeder:
				DropDown(false, false);

				break;
			case GantryMovingBinToPurgeArea:
				DropDown(false, true);
				break;
			case GantryDroppingOffEmptyBin:
				DropDown(false, true);
				break;
			case GantryGettingEmptyBin:
				DropDown(true, false);
				break;
			case GantryFillingEmptyBin:
				FillBinWithParts();
				DropOffNewBin();
				gantryAgent->setBin(newBinType, currentBinNum);
				break;
			case GantryDroppingOffNewBin:
				DropDown(false, true);
		}
	}
	else
	{
		int randX = rand() % (int)factoryScene->width();
		int randY = rand() % (int)factoryScene->height();
		moveTo(randX, randY);
	}
}

void GUIGantry::doneScaling()
{ //animation complete 
	if (!exploding)
	{
		GUIFeeder* feeder;

		switch(vertState)
		{
		case GantryDropping:
			if (dropAndPickUpBin)
			{
				this->LatchOntoBin();
			}
			else if (dropAndLetGoOfBin)
			{
				this->UnlatchFromBin();
			}
			
			switch (state)
			{
			case GantryMovingToFeeder:
				DumpBinIntoFeeder();
				break;
			}

			PullUp();
			break;
		case GantryRising:
			switch(state)
			{
			case GantryPickingUpFullBin:
				// Check if bin is a purge bin, and if so then tell the feeder the bin is no longer available.
				feeder = currentBinRef->GetFeeder();
				if (feeder != NULL)
				{
					feeder->HereIsPurgeBin(NULL);
					currentBinRef->SetFeeder(NULL);
				}

				GoToFeeder();
				break;
			case GantryMovingToFeeder:
				if (currentFeederRef->hasPurgeBin())
					DropOffEmptyBin();
				else
					PutBinInPurgeArea();
				break;
			case GantryMovingBinToPurgeArea:
				currentBinRef->SetFeeder(currentFeederRef);
				currentFeederRef->HereIsPurgeBin(currentBinRef);
				gantryAgent->msgGUIHalfDoneMoving(currentBinNum);
				break;
			case GantryDroppingOffEmptyBin:
				gantryAgent->msgGUIHalfDoneMoving(currentBinNum);
				break;
			case GantryGettingEmptyBin:
				// Check if bin is a purge bin, and if so then tell the feeder the bin is no longer available.
				feeder = currentBinRef->GetFeeder();
				if (feeder != NULL)
				{
					feeder->HereIsPurgeBin(NULL);
					currentBinRef->SetFeeder(NULL);
				}

				FillEmptyBin();
				break;
			case GantryDroppingOffNewBin:
				gantryAgent->msgGUIDoneMoving();
				break;
			}
			vertState = GantryStable;
			break;
		}
	}
	else
	{
		this->scaleOverTime(((1.5-0.9) * ((float)rand() / RAND_MAX)) + 0.9);
	}
}

void GUIGantry::doneRotating()
{ //won't call next action until done rotating 
	if (exploding)
	{
		this->rotateOverTime(rand() % 90 + 270, !clockwiseRotation);

		if (!this->childItems().empty())
		{
			if (currentBinRef->GetPartCount() > 0)
			{
				Part* part = new Part(this->scenePos().x(), this->scenePos().y(), currentBinRef->GetPartType());
				int randX = (rand() % 200) - 100 + this->scenePos().x();
				int randY = (rand() % 200) - 100 + this->scenePos().y();
				part->setMovementSpeed(10);
				part->accelTo(randX, randY, -0.7, 0);
				part->PartFellOnFloor();
			}
		}
	}
}


void GUIGantry::advance(int phase)
{ 
	if (phase == 1)
	{
		if (exploding)
		{
			// Eratic movements
			this->setMovementSpeed(((GANTRY_EXPLODE_MOVEMENT_SPEED_MAX - GANTRY_EXPLODE_MOVEMENT_SPEED_MIN) * ((float)rand() / RAND_MAX)) + GANTRY_EXPLODE_MOVEMENT_SPEED_MIN);
			this->setRotationSpeed(((GANTRY_EXPLODE_ROTATION_SPEED_MAX - GANTRY_EXPLODE_ROTATION_SPEED_MIN) * ((float)rand() / RAND_MAX)) + GANTRY_EXPLODE_ROTATION_SPEED_MIN);
			this->setScaleSpeed(((GANTRY_EXPLODE_VERT_SPEED_MAX - GANTRY_EXPLODE_VERT_SPEED_MIN) * ((float)rand() / RAND_MAX)) + GANTRY_EXPLODE_VERT_SPEED_MIN);

			// Advance the animation.
			static int explodeAnimCounter = 0;
			explodeAnimCounter = (explodeAnimCounter + 1) % 10;
			if (explodeAnimCounter == 9)
				currentFrame = (currentFrame + 1) % 2;

			/*
			if (!this->childItems().empty())
			{
				if (currentBinRef->GetPartCount() > 0)
				{
					// Throw parts every so often.
					static int partThrowerCounter = 0;
					partThrowerCounter = (partThrowerCounter + 1) % 50;
					if (partThrowerCounter == 49)
					{
						Part* part = currentBinRef->TakeTopPartOut();
						int randX = (rand() % 200) - 100 + this->scenePos().x();
						int randY = (rand() % 200) - 100 + this->scenePos().y();
						part->setMovementSpeed(10);
						part->accelTo(randX, randY, -0.7, 0);
					}
				}
			}
			*/
		}
	}
	GUIBase::advance(phase);
}
